#pragma once

#include "json.h"
#include "schema.h"

namespace Json
{

class Database;

struct Record
{
	Record() : schema("Null"), revision(0) {}

	std::string schema;
	int revision;
	Ut::SharedPtr<JObject> pObject;
};

class Folder
{
public:
	Folder* InsertFolder(const std::string& name);
	Record* InsertRecord(const std::string& name);

	bool Rename(const std::string& oldName, const std::string& newName);
	void Delete(const std::string& name);

	Folder* GetFolder(const std::string& name);
	Record* GetRecord(const std::string& name);
	Record* FindRecord(const std::string& location);
	Folder* FindFolder(const std::string& location);

	typedef Ut::Range<std::map<std::string, Folder>::iterator> FolderRange;
	typedef Ut::Range<std::map<std::string, Record>::iterator> RecordRange;

	FolderRange GetFolderRange();
	RecordRange GetRecordRange();

	SERIALISATION_DECL(Folder)

private:
	friend Database;

	bool IsValidName(const std::string newName) const;

	std::map<std::string, Folder> m_folders;
	std::map<std::string, Record> m_records;
};

struct NullObject
{
};

typedef void (FindReferencesFn)(const Json::Database& database, NullObject& t);

class RecordIterator
{
public:
	RecordIterator() {}

	explicit RecordIterator(Folder::FolderRange::Iterator& root);
	bool operator != (const RecordIterator& other) const;
	void operator ++() {++m_recordIterator; HandleEndRecords();}
	Record& operator *() {return m_recordIterator->second;}

private:
	void HandleEndRecords();
	void BeginFolder();
	void BeginRecords();

	std::vector<std::pair<Folder::FolderRange, Folder::FolderRange::Iterator>> m_folderStack;
	Folder::RecordRange::Iterator m_recordIterator;
};

class Database : private Folder
{
public:
	Database();

	void SetName(const std::string& name);
	const std::string& GetName() const;

	Schema::Object* InsertSchema(const std::string& name);
	Schema::Object* GetSchema(const std::string& name);
	const Schema::Object* GetSchema(const std::string& name) const;
	void EraseSchema(const std::string& name);
	void RenameSchema(const std::string& oldName, const std::string& newName);

	typedef Ut::Range<std::map<std::string, Schema::Object>::const_iterator> SchemaRange;
	SchemaRange GetSchemaRange() const;

	Schema::Enum* InsertEnum(const std::string& name);
	Schema::Enum* GetEnum(const std::string& name);
	void EraseEnum(const std::string& name);
	void RenameEnum(const std::string& oldName, const std::string& newName);

	typedef Ut::Range<std::map<std::string, Schema::Enum>::const_iterator> EnumRange;
	EnumRange GetEnumRange() const;

	typedef Ut::Range<RecordIterator> RecordRange;
	RecordRange GetRecordRange();

	Folder& GetRoot();

	void Schematise(Record& record, const Schema::Object& schema);
	
	bool WriteHeader(const char* filename) const;
	Record* FindRecord(const char* location);
	Folder* FindFolder(const char* location);
	const Record* FindRecord(const char* location) const;
	const Folder* FindFolder(const char* location) const;

	template <typename T>
	const T* GetObject(const char* location) const
	{
		auto it = m_objects.find(location);

		if (it == m_objects.end())
			return 0;

		return (T*) &*it->second;
	}

	void CreateObjects();
	void Register(const char* guid, FindReferencesFn* p);

	int IncrementRevision(); // increment then return revision.

	SERIALISATION_DECL_GUID(Json::Database)

private:
	void Schematise(JObject& object, int revision, const Schema::Object& schema, std::list<const Schema::Object*>& schemaStack);
	bool WriteDeclaration(std::ofstream& file, const std::string& name) const;
	bool WriteSchema(std::ofstream& file, const std::string& name, const Schema::Object& object) const;
	bool WriteEnum(std::ofstream& file, const std::string& name, const Schema::Enum& enum_) const;
	bool WriteRegisterTypes(std::ofstream& file) const;
	void CreateObjects(const std::string& path, const Folder& folder, bool findReferences);
	void CreateObject(const std::string& path, const std::string& name, const Record& record, bool findReferences);
	void Schematise(Json::JData* jData, int revision, Schema::Type basicType, const std::string typeName);

	std::string m_name;
	std::map<std::string, Schema::Object> m_schemas;
	std::map<std::string, Schema::Enum> m_enums;

	// Holds pointers to game objects.
	std::map<std::string, Ut::SharedPtr<NullObject>> m_objects;

	std::map<std::string, FindReferencesFn*> m_findReferenceMap;

	// Temporary data for writing headers.
	mutable std::set<std::string> m_written;
	mutable std::set<std::string> m_started;

	int m_revision;
};

}

#define DATABASE_REGISTER(database, name) \
										{ \
											typedef void (FindReferencesFn)(const Json::Database& database, name& t); \
											database.Register(name::GetSerialisationGuid_Static(), (Json::FindReferencesFn*)(FindReferencesFn*) &FindReferences); \
										}